<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * 角色函数库
 * 
 * @desc 
 * @version 1.0.0 
 * @author  Jason
 * @date	2017年6月16日 下午2:31:44
 */
class Lib_role extends Lib_database
{
	/** @var Mod_role */
	private $mod_role = null;
	
	use libraries\common\DatabaseCURD;
	
	use libraries\common\ConditionParser;
	
	public function __construct()
	{
		parent::__construct();
		
		$this->CI->load->model('mod_role');
		$this->mod_role =& $this->CI->mod_role;
		
		$this->set_render_config();
	}
	
	private function set_render_config()
	{
		$foreign_keys = [
				'master' => [
				],
				'action' => [
				],
				'acl' => [
						['role_id', ['name', 'description', 'status'], $this->mod_role, 'master', 'id'],
						['action_id', ['code', 'name', 'description'], $this->mod_role, 'action', 'id'],
				],
		];
		$time_fields = [
				'master' => [],
				'action' => [],
				'acl' => [],
		];
		$encode_fields = [
				'master' => [],
				'action' => [],
				'acl' => [],
		];
		$this->foreign_keys = $foreign_keys;
		$this->time_fields = $time_fields;
		$this->encode_fields = $encode_fields;
	}
	
	public function get_roles_for_list($conditions, $page = 1, $number = 12)
	{
		$data = $this->mod_role->get_list($conditions, $page, $number);
		$data['data'] = $this->render_list($data['data'], 'master', false, [
				[$this, 'parse_role_item'],
		]);
		return $data;
	}
	
	public function get_actions_for_list($conditions, $page = 1, $number = 12)
	{
		$mapping = 'action';
		$data = $this->mod_role->get_list($conditions, $page, $number, $mapping);
		$data['data'] = $this->render_list($data['data'], $mapping);
		return $data;
	}
	
	public function get_acls_for_list($conditions, $page = 1, $number = 12)
	{
		$mapping = 'acl';
		$data = $this->mod_role->get_list($conditions, $page, $number, $mapping);
		$data['data'] = $this->render_list($data['data'], $mapping);
		return $data;
	}
	
	public function get_role_for_detail($id)
	{
		$item = $this->mod_role->select_instance_by_id($id);
		$item = $this->render_list($item, 'master', true, [
				[$this, 'parse_role_item'],
		]);
		return $item;
	}
	
	public function get_action_for_detail($id)
	{
		$mapping = 'action';
		$item = $this->mod_role->select_instance_by_id($id, $mapping);
		$item = $this->render_list($item, $mapping, true);
		return $item;
	}
	
	public function get_acl_for_detail($id)
	{
		$mapping = 'acl';
		$item = $this->mod_role->select_instance_by_id($id, $mapping);
		$item = $this->render_list($item, $mapping, true);
		return $item;
	}
	
	public function parse_role_item($item)
	{
		$item['status_tags'] = $this->mod_role->calculate_status_tags($item['status']);
		return $item;
	}
	
	/**
	 * 角色状态切换
	 *
	 * @author  Jason
	 * @date	2017年6月20日 下午7:07:45
	 *
	 * @param number $id
	 * @param string $positiveTag eg: 'enabled'
	 *
	 * @return [<status>, <content>]	一个两元素数组，第一个元素表示状态，第二个表示相应内容
	 * 	        eg:	[true, ['nowTag' => 'enabled']]
	 * 				[true, ['nowTag' => 'disabled']]
	 * 				[false, ['error']]
	 */
	public function toggle_status_positive_tag()
	{
		$this->CI->load->library('form_validation');
		
		$status = false;
		$content = [];
		if(FALSE === $this->CI->form_validation->run("toggle_status_positive_tag")) {
			$errorKey = $this->CI->form_validation->error_array();
			$status = false;
			$content = $errorKey;
		} else {
			$id = $this->CI->input->post('id');
			$positiveTag = $this->CI->input->post('tag');
			
			$status = true;
			$ultimateTag = $this->mod_role->toggle_status_by_tag($id, $positiveTag);
			$content = ['nowTag' => $ultimateTag];
		}
		
		return [$status, $content];
	}
	
	/**
	 * 更新（刷新）角色的 所有 Action
	 *
	 * @author  Jason
	 * @date	2017年6月26日 下午4:51:26
	 *
	 * @param number 		$role_id
	 * @param string/array	$action_ids	ActionIDs，权限ID
	 *
	 * @return [<status>, <content>]	一个两元素数组，第一个元素表示状态，第二个表示相应内容
	 * 	        eg:	[true, []]
	 * 				[false, ['error']]
	 */
	public function flush_acl()
	{
		$this->CI->load->library('form_validation');
		
		// MARK 如果你想验证多个数组，那么你应该在验证下一个新数组之前先调用 reset_validation() 方法。
		$this->CI->form_validation->reset_validation();
		// MARK POST 中包含数组参数并且有多个值时，action_ids[]&action_ids[]，相应的 callback 会调用多次！
		// 所以，这种处理要放在表单验证之前！不能依赖于底层 库函数 甚至 模型！
		# $this->CI->form_validation->run("role_acl_flush");exit;
		// isset($_POST['action_ids']) && is_array($_POST['action_ids']) OR \
		if(is_array($this->CI->input->post('action_ids'))) {
			$_POST['action_ids'] = implode(',', $_POST['action_ids']);
		}
		
		$status = false;
		$content = [];
		if(FALSE === $this->CI->form_validation->run("role_acl_flush")) {
			$errorKey = $this->CI->form_validation->error_array();
			$status = false;
			$content = $errorKey;
		} else {
			$role_id = $this->CI->input->post('role_id');
			$action_ids = $this->CI->input->post('action_ids');
			
			$status = true;
			$this->mod_role->clear_role_actions($role_id);
			$this->mod_role->set_role_actions($role_id, $action_ids);
		}
		
		return [$status, $content];
	}
	
	/**
	 * 指定ID的角色是否已经存在
	 *
	 * @desc	适用场景：在添加广告时指定角色ID，这个角色是需要存在的！
	 * @date	2017年6月16日 下午1:12:08
	 * @desc	适用场景：在添加 ACL 时指定角色ID，这个角色是需要存在的！
	 * @date	2017年6月20日 下午5:38:45
	 *
	 * @param number $role_id
	 *
	 * @return boolean
	 */
	public function has_role_exists($role_id)
	{
		$selected_id = $this->mod_role->select_fields_by_id($role_id, ['id'], 'master');
		return !empty($selected_id);
	}
	
	/**
	 * 指定ID的Action是否已经存在
	 *
	 * @desc	适用场景：在添加 ACL 时指定角色ID，这个角色是需要存在的！
	 * @date	2017年6月20日 下午5:38:33
	 *
	 * @param number $role_id
	 *
	 * @return boolean
	 */
	public function has_action_exists($action_id)
	{
		$selected_id = $this->mod_role->select_fields_by_id($action_id, ['id'], 'action');
		return !empty($selected_id);
	}
	
	/**
	 * 过滤提交过来的 Action 列表
	 *
	 * @desc	适用场景：在添加 角色 时如果同时指定 ActionIDs，这些 Action 是需要存在的！
	 * 			不过该函数不返回错误，静默过滤非法数据！
	 * 			MARK 但是，但是，但是，这种验证函数好像不能接 POST 过来的数组参数！需要在验证之前将数组打成 CSV 形式。
	 * 			MARK 然而，然而，然而，在返回的时候又可以是数组形式，一切功能正常！
	 * @date	2017年6月26日 下午5:16:19
	 *
	 * @param string/array	$action_ids
	 *
	 * @return boolean
	 */
	public function filter_action_list($action_ids)
	{
		//var_dump($action_ids); var_dump(123);
		if(empty($action_ids)) return array();
		if(is_string($action_ids)) {
			$action_ids = array_filter(explode(',', $action_ids), 'strlen');
		}
		
		$conditions = [
				['select', 'id'],
		];
		$origin_ids = $this->mod_role->select_instances_by_conditions($conditions, 'action');
		$origin_ids = array_column($origin_ids, 'id');
		
		$allowed_ids = array_intersect($origin_ids, $action_ids);
		
		//return implode(',', $allowed_ids);
		return $allowed_ids;
	}
	
	
	/**
	 * 重建 Action 通过反射机制
	 * 
	 * @desc    通过反射机制重置 Action，但是只增不减，原有的也不会更新！后者避免覆盖掉用户和改动。
	 * @author  Jason
	 * @date    2017年6月26日 下午2:53:42
	 * 
	 * @param   string 本次反射的所有类所在的直接上级目录名称
	 * 
	 * @return  number 新增加的数量
	 */
	public function rebuild_action_by_reflection($directory = 'manage')
	{
		Lib_docomment::spl_autoload_register_controller($directory);
		
		$dirName = APPPATH . "controllers/" . $directory;
		$classNames = Lib_docomment::getClassNames($dirName);
		
		$actionData = [];
		foreach ($classNames as $className) {
			$classObject = Lib_docomment::getClassObject($className);
			$docComment = Lib_docomment::getClassComment($classObject);
			$commands = explode(" ", Lib_docomment::getParam($docComment, 'command'));
			if(in_array('pass', $commands)){
				continue;
			}
			
			$action['group_code'] = lcfirst($classObject->getName());
			$action['group_name'] = Lib_docomment::getParam($docComment, 'title');
			
			$methodObjects = Lib_docomment::getMethodObjects($classObject);
			foreach ($methodObjects as $methodObject) {
				$docComment = Lib_docomment::getMethodComment($methodObject);
				$commands = explode(" ", Lib_docomment::getParam($docComment, 'command'));
				if(in_array('pass', $commands)){
					continue;
				}
				
				$action['code'] = "/" . lcfirst($directory) . "/" . lcfirst($classObject->getName()) . "/" . $methodObject->getName();
				$action['name'] = Lib_docomment::getParam($docComment, 'title');
				$action['description'] = Lib_docomment::getParam($docComment, 'desc');
				
				$actionData[] = $action;
			}
		}
		
		$conditions = [
				['select', 'code']
		];
		$originData = $this->mod_role->select_instances_by_conditions($conditions, 'action');
		$originData = array_column($originData, 'code');
		
		$insertData = array_filter($actionData, function($data) use ($originData) {
			return !in_array($data['code'], $originData);
		});
// 		print_r($originData);exit;
// 		print_r($insertData);exit;
		
		foreach ($insertData as $post) {
			$this->mod_role->create_instance($post, 'action');
		}
		
		$status = true;
		$content = [
				'count' => count($insertData),
				'data' => $insertData,
		];
		return [true, $content];
	}
}